<?php
///
namespace lxsphp\Core;

///
use lxsphp\App;

/**
 * 核心 - [错误/异常处理]类
 *
 * @version $Id: Handler.php 195 2014-07-18 09:35:22Z linsir123 $
 * @package lxsphp\Core
 */
class Handler
{
	/**
	 * 配置
	 *
	 * @var array
	 */
	public $conf = array();

	/**
	 * 系统错误类型
	 *
	 * @var array
	 * @static
	 */
	static $ERRORS = array(
		E_ERROR => 'ERROR',
		E_WARNING => 'WARNING',
		E_PARSE => 'PARSING ERROR',
		E_NOTICE => 'NOTICE',
		E_CORE_ERROR => 'CORE ERROR',
		E_CORE_WARNING => 'CORE WARNING',
		E_COMPILE_ERROR => 'COMPILE ERROR',
		E_COMPILE_WARNING => 'COMPILE WARNING',
		E_USER_ERROR => 'USER ERROR',
		E_USER_WARNING => 'USER WARNING',
		E_USER_NOTICE => 'USER NOTICE',
		E_STRICT => 'STRICT NOTICE',
		E_RECOVERABLE_ERROR => 'RECOVERABLE ERROR',
	);

	/**
	 * 异常是否通过Handler进行构造
	 *
	 * @var bool
	 */
	private $_eByThis = false;

	/**
	 * 构造函数
	 */
	public function __construct()
	{
		///
		$this->conf = App::o("app.handler");
	}

	/**
	 * 启动
	 */
	public function init()
	{
		///
		set_error_handler(array(& $this, 'error'), E_ALL);

		///
		set_exception_handler(array(& $this, 'exception'));
	}

	/**
	 * 自定义错误处理
	 * // 被“@”抑制后语句在出错的情况下不进入这方法
	 *
	 * @param int $errNum 错误代码
	 * @param string $errStr 错误内容
	 * @param string $errFile 错误文件
	 * @param int $errLine 错误文件行号
	 */
	public function error($errNum, $errStr, $errFile, $errLine)
	{
		///
		if (! (error_reporting() & $errNum)
			|| in_array($errNum, array(E_NOTICE, E_STRICT, E_USER_NOTICE))
		)
			return;

		///
		$type = array_key_exists($errNum, self::$ERRORS) ?
			self::$ERRORS[$errNum] : 'CAUGHT EXCEPTION';

		$content = sprintf("[%s] %s (%s, %s)",
			$type,
			$errStr,
			$errFile,
			$errLine
		);
		if ($this->conf['trace'])
			$content .= sprintf("\n[TRACE] => %s",
				$this->_getErrorTrace());

		///
		if ($this->conf['show'])
			echo nl2br($content);

		App::$I->getLog()->write($content, 'runtime/error');
	}

	/**
	 * 自定义异常处理
	 *
	 * @param \Exception $e 异常对象
	 */
	public function exception($e)
	{
		///
		try {
			$key = $e->getCode() == 404 ? 404 : 500;
			$data = $this->conf['controller'][$key];

			/// 1.1
			if (! empty($data) && is_array($data))
				App::$I->getDispatcher()->doController($data[0], $data[1], true);

			/// 1.2
			else
				$this->_formatException($e);

		} catch (\Exception $e) {

			/// 2
			$this->_formatException($e);
		}

		///
		App::$I->end();
	}

	/**
	 * 用于框架抛出异常并进行Log操作
	 *  // * 之前的设计没有考虑到APP会进行try{}catch操作，导致框加的异常不会被Log
	 *  // 需要进行日志记录的并且抛出异常才使用此方法
	 *  // 其它异常进行Throw操作
	 *
	 * @param string $message
	 * @param int $code
	 *
	 * @throws \Exception
	 */
	public function panic($message, $code = 500)
	{
		/// 1
		if (! $message instanceof \Exception) {
			$e = new \Exception($message, $code);
			$this->_eByThis = true;
		}
		else
			$e = $message;

		$content = $this->_formatException($e, false);
		App::$I->getLog()->write($content, 'runtime/exception');

		/// 2
		$this->exception($e);
	}

	/**
	 * 格式化异常
	 *
	 * @param \Exception $e
	 * @param bool $echo
	 *
	 * @return mixed
	 */
	private function _formatException($e, $echo = true)
	{
		///
		if (! $echo) {
			list($file, $line) = $this->_getExceptionFile($e);
			$content = sprintf("%s (%s, %s)", $e->getMessage(), $file, $line);
			if ($this->conf['trace'])
				$content .= sprintf("\n[TRACE] => %s\n",
					$e->getTraceAsString());

			return $content;
		}

		///
		if ($this->conf['show'])
			$this->_showException($e);

		///
		return null;
	}

	/**
	 * 以Html方式输出异常
	 *
	 * @param \Exception $e 异常对象
	 */
	private function _showException($e)
	{
		///
		echo <<< eof
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>系统异常_LXSPHP4.0</title>
<style type="text/css">
*{font-family: Verdana, Arial, Helvetica, sans-serif;}
body{background: #efefef;}
#container{width: 960px;margin: 0 auto;background: #ffffff;
font-size: 12px;padding: 5px;margin-top: 30px;}
#container div{padding: 3px;}
#container div span{display: block;padding: 3px;}
#container .title{font-weight: bold;text-align: center;font-size: 14px;
border-bottom: 1px solid #efefef;padding: 5px;}
</style>
</head>

<body>
	<div id="container">
		<div class="title">系统异常_LXSPHP4.0</div>
eof;

		///
		list($file, $line) = $this->_getExceptionFile($e);
		echo '<div>File: <b>', $file, '</b></div>';
		echo '<div>Message: <b>', $e->getMessage(), '</b></div>';
		echo '<div>Line: <b>', $line, '</b></div>';
		echo '<div>Trace: <br /><pre>', $e->getTraceAsString(), '</pre></div>';

		///
		echo <<< eof
	</div>
</body>
</html>
eof;
	}

	/**
	 * 取得异常出错的位置
	 *
	 * @param \Exception $e
	 *
	 * @return array
	 */
	private function _getExceptionFile($e)
	{
		///
		$list = $e->getTrace();
		if ($this->_eByThis && is_array($list) && isset($list[0]))
			return [$list[0]['file'], $list[0]['line']];

		///
		return [$e->getFile(), $e->getLine()];
	}

	/**
	 * 返回错误队列
	 *
	 * @return string
	 */
	private function _getErrorTrace()
	{
		///
		$content = "";
		$trace = debug_backtrace();
		if (count($trace) > 3)
			$trace = array_slice($trace, 3);

		foreach ($trace as $i => $t) {
			if (! isset($t['file']))
				$trace[$i]['file'] = 'unknown';

			if (! isset($t['line']))
				$trace[$i]['line'] = 0;

			if (! isset($t['function']))
				$trace[$i]['function'] = 'unknown';

			$content .= "#$i {$trace[$i]['file']}({$trace[$i]['line']}): ";
			if (isset($t['object']) && is_object($t['object']))
				$content .= get_class($t['object']) . '->';

			$content .= "{$trace[$i]['function']}()\n";

			unset($trace[$i]['object']);
		}

		///
		return $content;
	}
}
